<!DOCTYPE html>
<!--
BlendMol 1.3: Advanced Molecular Visualization in Blender. Copyright (C)
2019 Jacob D. Durrant

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
details.

You should have received a copy of the GNU General Public License along with
this program.  If not, see <http://www.gnu.org/licenses/>.
-->
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html" charset="utf-8" />
        <title>Michelangelo's Molecule</title>
        <style>
            html, body {
                width: 100%;
                height: 100%;
                padding: 0;
                margin: 0;
                overflow: hidden;
            }

            #renderCanvas {
                width: 100%;
                height: 100%;
            }
        </style>
    </head>
    <body>
        <!-- Create a canvas where the 3D scene will be drawn. -->
        <canvas id="renderCanvas"></canvas>

        <!-- Load the babylon library. Using a custom build of version 3.1
        stable that includes collisions using web workers, as well as wood and
        marble procedural textures. -->
        <script src="babylon.custom.js"></script>

        <!-- Load the jQuery library -->
        <script src="https://code.jquery.com/jquery-3.2.1.js" integrity="sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE=" crossorigin="anonymous"></script>

        <script>
            // Keep track of when the VR camera last moved. This is to prevent
            // rapid movements in VR mode, which can make people sick.
            var VRMovedLastTime = 0;

            // Keep track of the camera name. When the user switches to a VR
            // headset, for example, this name will change.
            var lastCameraName = "";

            /**
            * Projects a vector onto the floor directly beneath it.
            *
            * @param {BABYLON.Vector3} pos - The vector to project onto the
            *     floor.
            * @param {BABYLON.Scene} scene - The scene where the vector
            *     resides.
            * @return {BABYLON.Vector3} A vector on the floor, directly
            *     beneath pos.
            */
            function vecProjectToFloor(pos, scene) {
                // Create a ray pointing downward.
                var rayOrigin = new BABYLON.Vector3(pos.x, 10, pos.z);
                var ray = new BABYLON.Ray(
                    rayOrigin, new BABYLON.Vector3(0, -1, 0), 50
                );

                // Determine where it hits the floor below.
                let pickingInfo = scene.pickWithRay(ray, function(mesh) {
                    return (mesh.id === "floor");
                });

                if (pickingInfo.hit) {
                    // Move the floor point up 1.0 units (so hovering above
                    // the floor).
                    pickingInfo.pickedPoint.y = pickingInfo.pickedPoint.y + 1.0;
                    return pickingInfo.pickedPoint;
                } else {
                    // There is no floor below, so just return the input
                    // point.
                    return pos;
                }
            }

            /**
            * Determines where the user is staring in the 3D world and moves
            * the camera to that location.
            *
            * @param {BABYLON.Scene} scene - The scene where the vector
            *     resides.
            */
            function moveCameraToStareLoc(scene) {
                // Determine the point on the floor where the user is staring.
                var newLoc = starePointOnTheFloor(scene);
                if (newLoc !== null) {
                    // The user is staring at a valid point, so change the
                    // camera position to that location.
                    scene.activeCamera.position = newLoc;
                }
            }

            /**
            * Determines where the user is staring in the 3D world.
            *
            * @param {BABYLON.Scene} scene - The scene where the vector
            *     resides.
            * @return {BABYLON.Vector3} A vector on the floor, where the user
            *     is staring.
            */
            function starePointOnTheFloor(scene) {
                // Get a ray extending out in the direction of the stare.
                var ray = scene.activeCamera.getForwardRay();

                // Determines where that ray intersects the floor.
                var pickingInfo = scene.pickWithRay(ray, function(mesh) {
                    return (mesh.id === "floor");
                });
                if (pickingInfo.hit) {
                    // It does hit the floor. Return the point.
                    return pickingInfo.pickedPoint;
                } else {
                    // It doesn't hit the floor, so return null.
                    return null;
                }
            }

            /**
            * If the camera name has recently changed, sets up the camera.
            *
            * @param {BABYLON.Scene} scene - The scene where the vector
            *     resides.
            */
            function setupCameraIfNeeded(scene) {
                // Get the active camera.
                var camera = scene.activeCamera;

                // Exit function if the camera name hasn't changed since last
                // check.
                if (camera.name === lastCameraName) {
                    return;
                }

                // Update the camera name with the new one.
                lastCameraName = camera.name;

                // Enable navigation via both WASD and the arrows keys.
                camera.keysUp = [87, 38];
                camera.keysDown = [83, 40];
                camera.keysLeft = [65, 37];
                camera.keysRight = [68, 39];

                // Enable collision detection
                // scene.workerCollisions = true;
                scene.collisionsEnabled = true;
                camera.ellipsoid = new BABYLON.Vector3(0.5, 0.5, 0.5);
                camera.checkCollisions = true;
                scene.getMeshByID("walls").checkCollisions = true;
                scene.getMeshByID("DavidBase").checkCollisions = true;

                // Slow the camera.
                camera.speed = 0.1;
            }

            // Check if BABYLON is supported.
            if (BABYLON.Engine.isSupported()) {
                // Initialize BabylonJS variables
                var canvas = jQuery("#renderCanvas").get(0);
                var engine = new BABYLON.Engine(canvas, true);

                // Wait until the DOM is ready.
                jQuery(document).ready(() => {

                    // Load the babylon file.
                    BABYLON.SceneLoader.Load(
                        "", "scene.babylon", engine,
                        (newScene) => {
                            window.scene = newScene;  // for debugging

                            // Wait for the new scene to be ready.
                            newScene.executeWhenReady(() => {
                                // Apply a marble texture to the floor.
                                var marbleTex = new BABYLON.MarbleProceduralTexture(
                                    "marble", 512, newScene
                                );
                                marbleTex.numberOfTilesHeight = 3;
                                marbleTex.numberOfTilesWidth = 3;
                                marbleTex.uScale = 5;
                                marbleTex.vScale = 5;
                                newScene.getMeshByID("floor").material.ambientTexture = marbleTex;

                                // Set up the VR helper, so the user can
                                // easily enter VR mode.
                                VRHelper = newScene.createDefaultVRExperience();

                                // Check if the user has triggered the
                                // controller.
                                VRHelper.onControllerMeshLoaded.add((cntrl) => {
                                    cntrl.onTriggerStateChangedObservable.add(() => {
                                        // The controller has been triggered.
                                        // Check if enough time has passed
                                        // since it was last triggered.
                                        let curTime = new Date().getTime();
                                        if (curTime - VRMovedLastTime > 500) {
                                            // Enough time has passed for a
                                            // new trigger. Update the
                                            // timestamp.
                                            VRMovedLastTime = curTime;

                                            // Move to where the user is
                                            // staring.
                                            moveCameraToStareLoc(newScene);
                                        }
                                    });
                                });

                                // If the user presses the spacebar, move to
                                // the location where he or she is staring.
                                jQuery(document).keydown((event) => {
                                    if (event.which === 32) {
                                        moveCameraToStareLoc(newScene);
                                    }
                                });

                                // Create a sphere to indicate where the user
                                // is staring.
                                var stareSphere = BABYLON.MeshBuilder.CreateSphere(
                                    "stareSphere", {diameter: 0.1}, newScene
                                );

                                // Start the render loop. The goal is to
                                // achieve 60 fps.
                                engine.runRenderLoop(() => {
                                    // Setup the camera if needed. Checks,
                                    // too, for a camera change (e.g., if the
                                    // webVR camera was activated).
                                    setupCameraIfNeeded(newScene);

                                    // Move the camera position to the floor,
                                    // so the user cannot walk up into the
                                    // sky.
                                    newScene.activeCamera.position = vecProjectToFloor(
                                        newScene.activeCamera.position, newScene
                                    );

                                    // Determine where the user is staring.
                                    // Move the stare sphere to that location.
                                    var stareLoc = starePointOnTheFloor(newScene);
                                    var stareSphere = newScene.getMeshByID("stareSphere");
                                    if (stareLoc !== null) {
                                        stareSphere.position = stareLoc;
                                        stareSphere.isVisible = true;
                                    } else {
                                        stareSphere.isVisible = false;
                                    }

                                    // Render the scene.
                                    newScene.render();
                                });

                                // Set the resize event handler.
                                window.addEventListener('resize', () => {
                                    engine.resize();
                                });

                            });
                        }, function (progress) {}
                    );
                });
            }
        </script>
    </body>
</html>
